/**
 * 变量环境 和 词法环境
 * let和块级作用域 到底是如何实现的?
 */

/*function fn() {
  var a = 1;
  let b = 2;

  { // 第一个代码块
    let b = 3;
    var c = 4;
    let d = 5;
    console.log(a, b, c, d); //  1,3,4,5
  }

  { // 第二个代码块
    let b = 6;
    let d = 7;
    console.log(a, b, c, d); // 1,6,4,7
  }
}

fn();*/

/**=== === === ↓↓↓伪代码↓↓↓ === === ===*/
function getValue(name, ec){
  /** 先在自己的词法栈里找*/
  for(let i = ec.lexicalEnvironment.length - 1; i>=0; i--) {
    if(name in ec.lexicalEnvironment[i]){
      return ec.lexicalEnvironment[i][name];
    }
  }

  /** 再从变量环境找*/
  if(name in ec.variableEnvironment){
    return ec.variableEnvironment[name];
  }

  /** 再从上级作用域找(上级作用域里的词法栈、变量环境->再上次...如此循环)*/
  if(ec.outer){
    return getValue(name, ec.outer);
  }

  return null;
}

/** 全局编译阶段 */
//创建全局执行上下文
const globalEC/**EC Execution Context*/ = {
  // this: globalThis // 代表当前this指针
  this: this

  // ES3里用scopeChain来存放作用域链
  // ES6里用outer来存放
  , outer: null // 外部执行上下文环境

  // ES3里会创建一个VO(VariableObject)来存放变量 当它激活的时候 就成了AO(ActivationObject)
  // 但ES6里就没有这东西了,而改成了叫 VariableEnvironment(变量环境) 用来存放 var function 声明的的变量
  ,VariableEnvironment: {
    fn(){ /*...*/ }
  }

  // 另外相较ES3还多了一个lexicalEnvironment(词法环境) 用来存放let const 声明的变量
  /** 以及在块级作用域里function声明的变量 参看 块级作用域对var和function声明的影响/index.js*/
  ,lexicalEnvironment: {}
};

/** ↓*/

/** 全局执行阶段*/

/** ↓*/

/** fn执行*/
/** fn编译阶段*/
// 创建fn执行上下文
const fnEC = {
  this: this

  /*静态作用域:一个函数的作用域是在它创建的时候决定的而不是执行的时候,是静态的*/
  ,outer: globalEC

  ,variableEnvironment: {a: undefined, c: undefined}

  ,lexicalEnvironment: [{b: undefined}]
};

/** ↓*/

/** fn执行阶段*/
// var a = 1;
fnEC.variableEnvironment.a = 1;
// let b = 2;
fnEC.variableEnvironment.b = 2;

/** ↓fn执行进入第一个代码块*/
/*
{ // 第一个代码块+
  let b = 3;
  var c = 4;
  let d = 5;
  console.log(a, b, c, d); //  1,3,4,5
}
*/
/** 每当函数执行的时候遇到一个新的代码块(块级作用域)`{}`,就会创建一个新的词法环境*/
fnEC.lexicalEnvironment.push({b: undefined, d: undefined}); // 注意如果此时console.log(b)或则d 那么会报错 (暂时性死区,意思是有变量提升但是此时不能用(直到运行到该变量声明的这一行))
/** 创建完后,代码块里的代码从上往下执行 */
fnEC.lexicalEnvironment[1].b = 3;
fnEC.variableEnvironment.c = 4; /** 会先在自己的词法环境对象里找c,没有,则往词法环境栈的下层找,直到找到栈底,如果栈底依然没有,则会从当前执行上下文中的 variableEnvironment 对象里找, 如果还没有则会顺着作用域链往上查找*/
fnEC.lexicalEnvironment[1].d = 5;

// console.log(a, b, c, d);
console.log(getValue('a', fnEC), getValue('b', fnEC), getValue('c', fnEC), getValue('d', fnEC));


/** 第一个代码块执行完了,出栈*/
fnEC.lexicalEnvironment.pop();

/** ↓*/

/** ↓fn执行进入第二个代码块*/
/*{ // 第二个代码块
  let b = 6;
  let d = 7;
  console.log(a, b, c, d); // 1,6,4,7
}*/

fnEC.lexicalEnvironment.push({b: undefined, d: undefined});

fnEC.lexicalEnvironment[1].b = 6;
fnEC.lexicalEnvironment[1].d = 7;
console.log(getValue('a', fnEC), getValue('b', fnEC), getValue('c', fnEC), getValue('d', fnEC));
